#include <string>
#include <vector>
#include <cstdio>
#include <cassert>

#include "ceph_mock/Objecter.h"

#include "./BenchmarkWorkloadBase.h"
#include "./PMKVTestSetBase.h"

using namespace std;
using namespace ceph_mock;


/**************** TEST CONFIGURATIONS ****************/

#include "./common_config.h"

namespace {
const struct {
    vector<unsigned> thread_nrs = {1, 4, 8, 16};
    size_t num_create = 5e3;
} test_config;
}


/**************** TEST CASES ****************/

struct TestCreate : BenchmarkWorkloadBase {
    explicit TestCreate(unsigned thread_nr, size_t num_create)
        : BenchmarkWorkloadBase("osd_op_create", thread_nr, num_create)
    { }

    /**
     * we start with an empty metadata pool, do nothing
     */
    void init(TestSetBase *base_test_ctx) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        assert(testctx);
        assert(testctx->objecter);
    }

    void workload(TestSetBase *base_test_ctx,
        unsigned thread_id, uint64_t seed) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        char oid[512];
        std::sprintf(oid, "test_%s_thrd%u_%lu", name.c_str(), thread_id, seed);
        // std::cerr << "creating obj " << oid << std::endl;
        Objecter::Op op(oid, {CEPH_OSD_OP_CREATE,});
        testctx->objecter->op_submit(&op);
    }

    /**
     * clear the metadata pool
     */
    void cleanup(TestSetBase *base_test_ctx) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        assert(testctx);
        testctx->clean_all();
    }
};  /* struct TestCreate */


struct TestWriteNoShare : BenchmarkWorkloadBase {
    vector<string> thrd_obj;

    explicit TestWriteNoShare(unsigned thread_nr, size_t min_runs)
        : BenchmarkWorkloadBase("osd_op_write_noshare", thread_nr, min_runs)
    {
        for (unsigned t = 0; t < thread_nr; t++) {
            char oid[512];
            std::sprintf(oid, "test_%s_thrd%u", name.c_str(), t);
            thrd_obj.push_back(oid);
        }
    }

    /**
     * create 4MB object for each thread
     * @param base_test_ctx 
     */
    void init(TestSetBase *base_test_ctx) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        assert(testctx);
        for (const auto &oid : thrd_obj) {
            Objecter::Op op(oid, {CEPH_OSD_OP_CREATE,});
            testctx->objecter->op_submit(&op);
        }
    }

    void workload(TestSetBase *base_test_ctx,
        unsigned thread_id, uint64_t seed) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        Objecter::Op op(thrd_obj[thread_id], {CEPH_OSD_OP_WRITE,});
        op.ops.front().op.extent.offset = (std::hash<uint64_t>{}(seed) % 1024) * 4096;
        testctx->objecter->op_submit(&op);
    }

    void cleanup(TestSetBase *base_test_ctx) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        testctx->clean_all();
    }
};  /* struct TestWriteNoShare */


struct TestReadNoShare : BenchmarkWorkloadBase {
    vector<string> thrd_obj;

    explicit TestReadNoShare(unsigned thread_nr, size_t min_runs)
        : BenchmarkWorkloadBase("osd_op_read_noshare", thread_nr, min_runs)
    {
        for (unsigned t = 0; t < thread_nr; t++) {
            char oid[512];
            std::sprintf(oid, "test_%s_thrd%u", name.c_str(), t);
            thrd_obj.push_back(oid);
        }
    }

    /**
     * create 4MB object for each thread
     * @param base_test_ctx 
     */
    void init(TestSetBase *base_test_ctx) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        assert(testctx);
        for (const auto &oid : thrd_obj) {
            Objecter::Op op(oid, {CEPH_OSD_OP_CREATE,});
            testctx->objecter->op_submit(&op);
        }
    }

    void workload(TestSetBase *base_test_ctx,
        unsigned thread_id, uint64_t seed) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        Objecter::Op op(thrd_obj[thread_id], {CEPH_OSD_OP_READ,});
        op.ops.front().op.extent.offset = (std::hash<uint64_t>{}(seed) % 1024) * 4096;
        testctx->objecter->op_submit(&op);
    }

    void cleanup(TestSetBase *base_test_ctx) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        testctx->clean_all();
    }
};  /* struct TestReadNoShare */


struct TestReadAllShare : BenchmarkWorkloadBase {
    string obj;

    explicit TestReadAllShare(unsigned thread_nr, size_t min_runs)
        : BenchmarkWorkloadBase("osd_op_read_single_obj", thread_nr, min_runs)
    {
        char oid[512];
        std::sprintf(oid, "test_%s", name.c_str());
        obj = oid;
    }

    /**
     * create 4MB object for each thread
     * @param base_test_ctx 
     */
    void init(TestSetBase *base_test_ctx) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        assert(testctx);
        Objecter::Op op(obj, {CEPH_OSD_OP_CREATE,});
        testctx->objecter->op_submit(&op);
    }

    void workload(TestSetBase *base_test_ctx,
        unsigned thread_id, uint64_t seed) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        Objecter::Op op(obj, {CEPH_OSD_OP_READ,});
        op.ops.front().op.extent.offset = (std::hash<uint64_t>{}(seed) % 1024) * 4096;
        testctx->objecter->op_submit(&op);
    }

    void cleanup(TestSetBase *base_test_ctx) const override
    {
        auto testctx = dynamic_cast<PMKVTestSetBase*>(base_test_ctx);
        testctx->clean_all();
    }
};  /* struct TestReadNoShare */


struct __pmkv_raw_osd_op_testset : PMKVTestSetBase {
    explicit __pmkv_raw_osd_op_testset(string_view rpmkv_path)
        : PMKVTestSetBase(__FILE__, rpmkv_path)
    {
        // for (const auto t : test_config.thread_nrs)
        //     test_cases.push_back(make_unique<TestCreate>(t, test_config.num_create));
        for (const auto t : test_config.thread_nrs)
            test_cases.push_back(make_unique<TestWriteNoShare>(t, test_config.num_create));
        for (const auto t : test_config.thread_nrs)
            test_cases.push_back(make_unique<TestReadNoShare>(t, test_config.num_create));
        // for (const auto t : test_config.thread_nrs)
        //     test_cases.push_back(make_unique<TestReadAllShare>(t, test_config.num_create));
    }
};  /* struct __pmkv_raw_osd_op_testset */

__pmkv_raw_osd_op_testset exported_testset(common_config.rpmkv_path);
